#ifndef Common_H
#define Common_H
#include "UnityCG.cginc"

float RadicalInverse_VdC(uint bits) {
    bits = (bits << 16u) | (bits >> 16u);
    bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);
    bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);
    bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);
    bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);
    return bits * 2.3283064365386963e-10f;
}

float2 Hammersley(uint index, uint nSamples) {
    return float2((float)index / nSamples, RadicalInverse_VdC(index));
}

// @return H
float3 ImportanceSampleGGX(float2 u, float roughness) {
	float alpha = roughness * roughness;
	float alpha2 = alpha * alpha;
	float phi = 2 * UNITY_PI * u.x;
	float cosTheta = sqrt((1 - u.y) / ( 1 + (alpha2 - 1) * u.y));
	float sinTheta = sqrt(1 - cosTheta * cosTheta);
	return float3(sinTheta * cos(phi), sinTheta * sin(phi), cosTheta);
}

float PdfGGX(float D, float NdotH, float VdotH) {
    return (D * NdotH) / (4 * VdotH);
}

// @return L
float3 ImportanceSampleLambertian(float2 u) {
    float r = sqrt(u.x); 
    float phi = u.y * UNITY_PI * 2; 
    return float3(r * cos(phi), r * sin(phi), sqrt(max(0.f, 1 - u.x))); 
}

float PdfLambertain(float NdotL) {
    return  NdotL * UNITY_INV_PI;
}

float D_GGX(float NdotH, float roughness) {
    float alpha = roughness * roughness;
	float alpha2 = alpha * alpha;
    float f = (NdotH * alpha2 - NdotH) * NdotH + 1;
    return alpha2 / (f * f * UNITY_PI);
}

float G_SmithGGX(float NdotL, float NdotV, float roughness) {
	float alpha = roughness * roughness;
	float alpha2 = alpha * alpha;
	float NdotL2 = NdotL * NdotL;
	float NdotV2 = NdotV * NdotV;
	float lambda_v = (-1 + sqrt (alpha2 * (1 - NdotL2 ) / NdotL2 + 1)) * 0.5;
	float lambda_l = (-1 + sqrt (alpha2 * (1 - NdotV2 ) / NdotV2 + 1)) * 0.5;
	return 1 / (1 + lambda_v + lambda_l);
}

float3 F_Schlick (float3 f0 , float f90 , float NdotL) {
	return f0 + (f90 - f0) * pow (1 - NdotL, 5);
}

float Fr_DisneyDiffuse(float NdotV, float NdotL, float LdotH, float linearRoughness) {
	float energyBias = lerp(0, 0.5, linearRoughness);
	float energyFactor = lerp(1.0, 1.0 / 1.51, linearRoughness);
	float fd90 = energyBias + 2.0 * LdotH * LdotH * linearRoughness;
	float3 f0 = float3(1, 1, 1);
	float lightScatter = F_Schlick(f0, fd90, NdotL).r;
	float viewScatter = F_Schlick(f0, fd90, NdotV).r;
	return lightScatter * viewScatter * energyFactor * UNITY_INV_PI;
}

float3 LatLongDirection(float2 uv) {
	float phi = (1.5 - uv.x * 2) * UNITY_PI;
	float theta = UNITY_PI * uv.y;
	float y = cos(theta);
	float sinTheta = sin(theta);
	float x = cos(phi) * sinTheta;
	float z = sin(phi) * sinTheta;
	return float3(x, y, z);
}

static const float3 g_axies[6][3] = {
	{ float3(1, 0, 0), 	    float3(0, 0, -1), 	   float3(0, -1, 0), },
	{ float3(-1, 0, 0),     float3(0, 0, 1), 	   float3(0, -1, 0), },
	{ float3(0, 1, 0), 	    float3(1, 0, 0), 	   float3(0, 0, 1), },
	{ float3(0, -1, 0),     float3(1, 0, 0), 	   float3(0, 0, -1), },
	{ float3(0, 0, 1), 	    float3(1, 0, 0), 	   float3(0, -1, 0), },
	{ float3(0, 0, -1),     float3(-1, 0, 0), 	   float3(0, -1, 0), },
};

float3 CubemapDirection(float2 uv, uint face) {
	float3 dir = g_axies[face][0];
	dir += (uv.x * 2 - 1) * g_axies[face][1];
	dir += (uv.y * 2 - 1) * g_axies[face][2];
	return normalize(dir);
}

float MipLevel2Roughness(uint mipLevel, uint nMipLevels) {
	float fmipLevel = (float)mipLevel / (nMipLevels - 1);
	float linearRoughness = fmipLevel * fmipLevel;
	return linearRoughness * linearRoughness;
}

float Roughness2MipLevel(float roughness, uint nMipLevels) {
	float linearRoughness = sqrt(roughness);
	float fmipLevel = sqrt(linearRoughness);
	fmipLevel = saturate(fmipLevel);
	return fmipLevel * (nMipLevels - 1);
}
#endif